//
// Created by edward on 22-11-17.
// 模拟5个哲学家进餐问题
//

#include "utils.h"
#include <mutex>
#include <atomic>
#include <string>
#include <thread>
#include <memory>

using namespace std;
atomic_bool flag = true;

constexpr int kMaxSleepTime = 10;
int n = 5;

//由于不能使用resize和emplace_back，只能使用动态分配内存，放在堆上了
//使用unique_ptr管理数组内存
//TODO:怀疑是因为std::mutex的某些type_traits还没有做好
unique_ptr<mutex[]> chopsticks;

template<typename Mtx>
class lock2_guard {
public:
    lock2_guard(Mtx &mtx0, Mtx &mtx1)
    : mtx0_(&mtx0)
    , mtx1_(&mtx1) {
        if (mtx1_ < mtx0_) swap(mtx0_, mtx1_);  //对地址小的先加锁
        mtx0_->lock();
        mtx1_->lock();
    }
    ~lock2_guard() {
        mtx1_->unlock();    //后加锁的先解锁，虽然好像没有什么区别
        mtx0_->unlock();
    }
    lock2_guard(const lock2_guard&) = delete;
    lock2_guard &operator= (const lock2_guard&) = delete;
private:
    Mtx *mtx0_, *mtx1_;
};

class Philosopher {
public:
    Philosopher(int idx, const string &name = string("philosopher ").append(to_string(UUID++)))
    : idx_(idx)
    , name_(name) {}
    void eat() {
        edward::print_safe(name_, "is now hungry");
        lock2_guard lk(chopsticks[idx_], chopsticks[(idx_ + 1) % n]); //获取左手边筷子和右手边筷子，对地址更小的筷子先加锁
        edward::print_safe(name_, "is now eating");
        this_thread::sleep_for(chrono::seconds(edward::Random::rand(kMaxSleepTime)));
    }
    void think() {
        edward::print_safe(name_, "is now thinking");
        this_thread::sleep_for(chrono::seconds(edward::Random::rand(kMaxSleepTime)));
    }
    void alive() {
        while (flag) {
            think();
            eat();
        }
    }
private:
    int idx_;   //根据idx_获取筷子，很重要
    const string name_;
    static int UUID;
};

int Philosopher::UUID = 0;

void work(int idx) {
    Philosopher(idx).alive();
}

int main() {
    edward::print("please input N:");
    cin >> n;
    chopsticks.reset(new mutex[n]);
    vector<thread> threads;
    for (int i = 0; i < n; ++i) {
        threads.emplace_back(work, i);
    }
    char c;
    while (cin >> c && c != 'n'); //等待输入n结束运行
    flag = false;
    for (int i = 0; i < n; ++i) {
        threads[i].join();
    }
    return 0;
}